#!/usr/bin/perl
# 
# Exercise 11.3
# 
# Parse the HEADER, TITLE, and KEYWORDS record types of all PDB files on 
# your computer. Make a hash with key as a word from those record types and value 
# as a list of filenames that contained that word. Save it as a DBM file and build a 
# query program for it. In the end, you should be able to ask for, say, sugar, and get a 
# list of all PDB files that contain that word in the HEADER, TITLE, or KEY-
# WORDS records.
#
# Answer to Exercise 11.3
#
# This exercises combines techniques that we've used in previous examples and exercises.
# First we'll use File::Find to locate all PDB files, similar to what we did in Exercise
# 11.2.  Then we'll use the subroutine "parsePDBrecordtypes" to access the HEADER TITLE
# and KEYWORDS record types, as we saw for instance in Example 11-5.  Then
# we'll extract the words from these record types as we did in Exercise 10.9 (which was
# mistakenly numbered 10.10 in the first printing of the book, but is numbered correctly
# in these answers.)  Finally, we'll go into a loop asking for user input (user interaction
# is often programmed with a loop).

use strict;
use warnings;
use File::Find;
use BeginPerlBioinfo;

# Search the directory "pdb" for pdb files, saving their names in the @pdbfiles array
my @pdbfiles = ();
find ( \&pdbfiles, 'pdb' );

# A hash to store the filenames of records, keyed by a word appearing in the record
my %words = ();

# A clearly inadequate list of words to ignore (can you do better?)
my %ignore = (
	'and' => 1,
	'or' => 1,
	'organism' => 1,
	'id' => 1,
	'if' => 1,
	'species' => 1,
	'header' => 1,
	'title' => 1,
	'keywords' => 1,
);

foreach my $pdbfile (@pdbfiles) {

	# Read in PDB file:  Warning - some files are very large!
	my @file = get_file_data($pdbfile);

	# Parse the record types of the PDB file
	my %recordtypes = parsePDBrecordtypes(@file);

	# Might as well put all the three desired record types into one scalar variable
	my $annotation = '';
	(defined $recordtypes{'HEADER'}) and ($annotation .= $recordtypes{'HEADER'});
	(defined $recordtypes{'TITLE'}) and ($annotation .= $recordtypes{'TITLE'});
	(defined $recordtypes{'KEYWORDS'}) and ($annotation .= $recordtypes{'KEYWORDS'});

	# Only need to handle each word once per PDB record
	my %seen = ();

	# Extract words, saving the byte offset for found words
	#  !! What is the definition of a word here?  Can you think of a better one for PDB annotations? !!
	while($annotation =~ /(\w[\w'-]*)/g) {

		# Store everything in lowercase
		my $word = lc $1;

		# Skip unwanted words, or words already found in this record
		defined $ignore{$word} and next;
		defined $seen{$word} and next;

		# Mark this new word as seen
		$seen{$word} = 1;

		# Add the filename of this record to the value for this word in the hash
		if(defined $words{$word}) {
			$words{$word} .= " $pdbfile";
		}else{
			$words{$word} = $pdbfile;
		}
	}
}

	
#
# Interact with the user, asking for words and showing the names of PDB files containing them
#

while( my $query = lc getuserinput("Find the PDB files containing what word?: ") ) {

	if(defined $words{$query}) {
		print $words{$query},"\n";
	}else{
		print "The word \"$query\" is not in the PDB files\n";
	}
}

exit;

################################################################################
# Subroutines
################################################################################
#
# getuserinput
#  - prompt the user for a one-line response
#    return null if response is an empty line, q, or quit
#    Otherwise, return answer (with leading and trailing whitespace removed)
#
sub getuserinput {
	my($prompt) = @_;

	print $prompt;
	my $ans = <STDIN>;
	chomp $ans;
	if($ans =~ /^\s*$/ or $ans =~ /^\s*q\s*$/i or $ans =~ /^\s*quit\s*$/i) {
		return '';
	}else{
		$ans =~ s/^\s*//;
		$ans =~ s/\s*$//;
		return $ans;
	}
}

# Find all files whose names begin with "pdb" and end with ".ent"
sub pdbfiles {
	# Ignore files that aren't ASCII text files or aren't readable
	-T and -r or return 0;

	 /^pdb.*\.ent$/ and push(@pdbfiles, "$File::Find::name");

	 return 0;
}

